/*----------------------------------------------------------------------------
 * Copyright (c) <2013-2015>, <Huawei Technologies Co., Ltd>
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *---------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 *---------------------------------------------------------------------------*/

#include "los_base.h"
#include "los_mux.h"
#include "los_hwi.h"
#include "errno.h"
#include "stdio.h"
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "dummy.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif /* __cplusplus */
#endif /* __cplusplus */

/*lint -esym(528,sfn_regulator_init2)*/
static UINT32 __regulator_list_lock;
static LOS_DL_LIST g_regdev_list;
static LOS_DL_LIST g_regconsum_list;
static bool has_full_constraints;


static const char *rdev_get_name(struct regulator_dev *rdev)
{
    if (!rdev) {
        return (char *)NULL;
    }
    if (rdev->constraints && rdev->constraints->name) {
        return rdev->constraints->name;
    } else if (rdev->desc->name) {
        return rdev->desc->name;
    } else {
        return (char *)NULL;
    }
}

static struct regulator_dev *regulator_dev_lookup(const char *supply,
                                                  int *ret)
{
    struct regulator_dev *r;
    const char *name;
    if (!supply) {
        goto out;
    }

    LOS_DL_LIST_FOR_EACH_ENTRY(r, &g_regdev_list, struct regulator_dev, list) {
        name = rdev_get_name(r);
        if (!name) {
            goto out;
        }
        if (strcmp(name, supply) == 0) {
            return r;
        }
    }

out:
    *ret = -ENODEV;
    return (struct regulator_dev *)NULL;
}



/* voltage constraint check */
static int regulator_check_voltage(struct regulator_dev *rdev,
                                   int *min_uV, int *max_uV)
{
    if (!rdev || !rdev->constraints || !min_uV || !max_uV) {
        dprintf("no constraints\n");
        return -ENODEV;
    }

    if (*min_uV > *max_uV) {
        return -EINVAL;
    }

    if (*min_uV < rdev->constraints->min_uV) {
        *min_uV = rdev->constraints->min_uV;
    }
    if (*max_uV > rdev->constraints->max_uV) {
        *max_uV = rdev->constraints->max_uV;
    }

    if (*min_uV > *max_uV) {
        dprintf("Invalid voltage range: %d-%d uV\n", *min_uV, *max_uV);
        return -EINVAL;
    }

    return 0;
}


/* current constraint check */
static int regulator_check_current_limit(struct regulator_dev *rdev,
                                         int *min_uA, int *max_uA)
{
    if (!rdev || !rdev->constraints || !min_uA || !max_uA) {
        dprintf("no constraints\n");
        return -ENODEV;
    }

    if (*min_uA > *max_uA) {
        return -EINVAL;
    }

    if (*min_uA < rdev->constraints->min_uA) {
        *min_uA = rdev->constraints->min_uA;
    }
    if (*max_uA > rdev->constraints->max_uA) {
        *max_uA = rdev->constraints->max_uA;
    }

    if (*min_uA > *max_uA) {
        dprintf("Invalid current range: %d-%duA\n", *min_uA, *max_uA);
        return -EINVAL;
    }

    return 0;
}




int regulator_is_enabled(struct regulator *reg)
{
    int ret = -EINVAL;

    if (!reg || !reg->rdev) {
        return ret;
    }
    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);
    if (reg->rdev->desc->ops->is_enabled) {
        ret = reg->rdev->desc->ops->is_enabled(reg->rdev);
    }

    (void)LOS_MuxPost(reg->rdev->lock);
    return ret;
}


int regulator_enable(struct regulator *reg)
{
    int ret = 0;
    if (!reg || !reg->rdev || !reg->rdev->desc || !reg->rdev->desc->ops) {
        return -EINVAL;
    }
    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);
    ret = regulator_is_enabled(reg);
    if (ret == 0) {
        if (reg->rdev->desc->ops->enable) {
            ret = reg->rdev->desc->ops->enable(reg->rdev);
        } else {
            ret = -EINVAL;
        }
    } else if (ret < 0) {
        dprintf("is_enabled() failed: %d\n", ret);
    }
    (void)LOS_MuxPost(reg->rdev->lock);
    return ret;
}


int regulator_disable(struct regulator *reg)
{
    int ret = -EINVAL;
    if (!reg || !reg->rdev || !reg->rdev->desc || !reg->rdev->desc->ops) {
        return -EINVAL;
    }
    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);
    if (reg->rdev->desc->ops->disable) {
        ret = reg->rdev->desc->ops->disable(reg->rdev);
    }
    (void)LOS_MuxPost(reg->rdev->lock);

    return ret;
}


int regulator_get_voltage(struct regulator *reg)
{
    int ret;
    if (!reg || !reg->rdev || !reg->rdev->desc || !reg->rdev->desc->ops) {
        return -EINVAL;
    }
    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);

    if (reg->rdev->desc->ops->get_voltage) {
        ret = reg->rdev->desc->ops->get_voltage(reg->rdev);
    } else if (reg->rdev->desc->fixed_uV && (reg->rdev->desc->n_voltages == 1)) {
        ret = reg->rdev->desc->fixed_uV;
    } else {
        ret = -EINVAL;
    }
    (void)LOS_MuxPost(reg->rdev->lock);

    return ret;
}



/* regulator_set_voltage - set regulator output voltage */
int regulator_set_voltage(struct regulator *reg, int min_uV, int max_uV, int set_volt)
{
    struct regulator_dev *rdev = (struct regulator_dev *)NULL;
    int ret = 0;
    int old_min_uV, old_max_uV;
    int cur_uV;

    if (!reg || !reg->rdev || !reg->rdev->desc ||
            !reg->rdev->desc->ops || !reg->rdev->constraints) {
        return -EINVAL;
    }
    rdev = reg->rdev;
    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);

    if (!(rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_VOLTAGE)) {
        ret = -EINVAL;
        goto out;
    }

    cur_uV = regulator_get_voltage(reg);
    if (min_uV <= cur_uV && cur_uV <= max_uV) {
        reg->min_uV = min_uV;
        reg->max_uV = max_uV;
    }

    /* do nothing */
    if (cur_uV == set_volt) {
        goto out;
    }

    /* sanity check */
    if (!rdev->desc->ops->set_voltage) {
        ret = -EINVAL;
        goto out;
    }

    /* constraints check */
    ret = regulator_check_voltage(rdev, &min_uV, &max_uV);
    if (ret < 0) {
        goto out;
    }

    /* restore original values in case of error */
    old_min_uV = reg->min_uV;
    old_max_uV = reg->max_uV;

    ret = rdev->desc->ops->set_voltage(rdev, set_volt);
    if (ret < 0) {
        reg->min_uV = old_min_uV;
        reg->max_uV = old_max_uV;
    }

out:
    (void)LOS_MuxPost(reg->rdev->lock);
    return ret;
}


int regulator_set_current_limit(struct regulator *reg,
                                int min_uA, int max_uA, int set_cur)
{
    struct regulator_dev *rdev = (struct regulator_dev *)NULL;
    int ret = 0;

    if (!reg || !reg->rdev || !reg->rdev->desc ||
            !reg->rdev->desc->ops || !reg->rdev->constraints) {
        return -EINVAL;
    }

    rdev = reg->rdev;
    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);
    if (!(rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_CURRENT)) {
        ret = -EINVAL;
        goto out;
    }

    /* do nothing */
    if (set_cur == regulator_get_current_limit(reg)) {
        goto out;
    }

    /* sanity check */
    if (!rdev->desc->ops->set_current_limit) {
        ret = -EINVAL;
        goto out;
    }

    /* constraints check */
    ret = regulator_check_current_limit(rdev, &min_uA, &max_uA);
    if (ret < 0) {
        goto out;
    }

    ret = rdev->desc->ops->set_current_limit(rdev, set_cur);
out:
    (void)LOS_MuxPost(reg->rdev->lock);
    return ret;
}


int regulator_get_current_limit(struct regulator *reg)
{
    int ret;

    if (!reg || !reg->rdev || !reg->rdev->desc || !reg->rdev->desc->ops) {
        return -EINVAL;
    }

    (void)LOS_MuxPend(reg->rdev->lock, LOS_WAIT_FOREVER);

    /* sanity check */
    if (!reg->rdev->desc->ops->get_current_limit) {
        ret = -EINVAL;
        goto out;
    }

    ret = reg->rdev->desc->ops->get_current_limit(reg->rdev);
out:
    (void)LOS_MuxPost(reg->rdev->lock);
    return ret;
}




static int machine_constraints_voltage(struct regulator_dev *rdev,
                                       struct regulation_constraints *constraints)
{
    int cur_uV;
    const struct regulator_ops *ops = (struct regulator_ops *)NULL;

    if (!rdev || !rdev->desc || !rdev->desc->ops || !constraints) {
        return -EINVAL;
    }

    if (constraints->min_uV > constraints->max_uV) {
        dprintf("Invalid voltage constraints\n");
        return -EINVAL;
    }

    ops = rdev->desc->ops;
    if (!ops->set_voltage || !ops->get_voltage) {
        dprintf("Operation of current configuration missing\n");
        return 0;
    }

    if (rdev->desc->n_voltages == 1) {
        constraints->min_uV = rdev->desc->fixed_uV;
        constraints->max_uV = rdev->desc->fixed_uV;
    } else {
        if (constraints->min_uV == constraints->max_uV) {
            return -EINVAL;
        }

        cur_uV = ops->get_voltage(rdev);
        if (cur_uV < constraints->min_uV || cur_uV > constraints->max_uV) {
            return -EINVAL;
        }
    }

    return 0;
}

static int machine_constraints_current(struct regulator_dev *rdev,
                                       struct regulation_constraints *constraints)
{
    int cur_uA;
    const struct regulator_ops *ops = (struct regulator_ops *)NULL;

    if (!rdev || !rdev->desc || !rdev->desc->ops || !constraints) {
        return -EINVAL;
    }

    if (constraints->min_uA > constraints->max_uA) {
        dprintf("Invalid current constraints\n");
        return -EINVAL;
    }

    ops = rdev->desc->ops;
    if (!ops->set_current_limit || !ops->get_current_limit) {
        dprintf("Operation of current configuration missing\n");
        return 0;
    }

    cur_uA = ops->get_current_limit(rdev);
    if (cur_uA < constraints->min_uA || cur_uA > constraints->max_uA) {
        return -EINVAL;
    }

    return 0;
}


static int set_machine_constraints(struct regulator_dev *rdev,
                                   struct regulation_constraints *constraints)
{
    int ret = 0;

    if (!rdev || !rdev->desc || !rdev->desc->ops || !constraints) {
        return -EINVAL;
    }
    rdev->constraints = (struct regulation_constraints *)malloc(sizeof(struct regulation_constraints));
    if (!rdev->constraints) {
        return -ENOMEM;
    }

    memcpy_s(rdev->constraints, sizeof(struct regulation_constraints), constraints, sizeof(struct regulation_constraints));

    if (rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_VOLTAGE) {
        ret = machine_constraints_voltage(rdev, rdev->constraints);
    } else {
        ret = machine_constraints_current(rdev, rdev->constraints);
    }
    if (ret != 0) {
        goto out;
    }

    return 0;
out:
    free(rdev->constraints);
    rdev->constraints = (struct regulation_constraints *)NULL;
    return ret;
}


void regulator_has_full_constraints(void)
{
    has_full_constraints = 1;
}



static bool have_full_constraints(void)
{
    return has_full_constraints ;
}


/**
 * regulator_register - register regulator
 * @regulator_desc: regulator to register
 * @cfg: runtime configuration for regulator
 *
 * Called by regulator drivers to register a regulator.
 * Returns a valid pointer to struct regulator_dev on success
 * or an ERR_PTR() on error.
 */
/* lint -e438 -e429 -e529*/
struct regulator_dev *
regulator_register(const struct regulator_desc *reg_desc,
                   const struct regulator_config *cfg)
{
    struct regulator_config *config = (struct regulator_config *)NULL;
    struct regulator_dev *rdev;
    int ret;

    if (!reg_desc || !cfg) {
        return (struct regulator_dev *)NULL;
    }

    if (!reg_desc->name || !reg_desc->ops) {
        return (struct regulator_dev *)NULL;
    }

    if (reg_desc->type != REGULATOR_VOLTAGE &&
            reg_desc->type != REGULATOR_CURRENT) {
        return (struct regulator_dev *)NULL;
    }

    rdev = (struct regulator_dev *)malloc(sizeof(struct regulator_dev));
    if (!rdev) {
        return (struct regulator_dev *)NULL;
    }

    config = (struct regulator_config *)malloc(sizeof(struct regulator_config));
    if (!config) {
        free(rdev);
        return (struct regulator_dev *)NULL;
    }
    memcpy_s(config, sizeof(struct regulator_config), cfg, sizeof(struct regulator_config));

    (void)LOS_MuxPend(__regulator_list_lock, LOS_WAIT_FOREVER);

    ret = LOS_MuxCreate(&rdev->lock);
    if (ret) {
        goto clean;
    }

    rdev->reg_data = config->driver_data;
    rdev->desc = reg_desc;

    /* preform any regulator specific init */
    if (config->regulator_init) {
        ret = config->regulator_init(rdev->reg_data);
        if (ret) {
            goto clean;
        }
    }

    rdev->dev = config->dev;

    dev_set_drvdata(&rdev->dev, rdev);

    ret = set_machine_constraints(rdev, &config->constraints);
    if (ret != 0) {
        goto clean;
    }

    LOS_ListTailInsert(&g_regdev_list, &rdev->list);
    LOS_ListTailInsert(&g_regconsum_list, &rdev->consumer_list);
    goto out;

clean:
    free(rdev);
    rdev = (struct regulator_dev *)NULL;

out:
    (void)LOS_MuxPost(__regulator_list_lock);
    free(config);
    return rdev;
}
/* lint +e438 +e429 +e529*/


/**
 * regulator_unregister - unregister regulator
 * @rdev: regulator to unregister
 *
 * Called by regulator drivers to unregister a regulator.
 */
void regulator_unregister(struct regulator_dev *rdev)
{
    if (!rdev) {
        return;
    }
    (void)LOS_MuxPend(__regulator_list_lock, LOS_WAIT_FOREVER);
    LOS_ListDelete(&rdev->list);
    free(rdev->constraints);
    rdev->constraints = (struct regulation_constraints *)NULL;
    device_unregister(&rdev->dev);
    free(rdev);
    (void)LOS_MuxPost(__regulator_list_lock);
    return;
}




#define REG_BUF_SIZE    64
static struct regulator *create_regulator(struct regulator_dev *rdev,
                                          const char *supply_name)
{
    struct regulator *reg;

    if (!rdev || !supply_name) {
        return (struct regulator *)NULL;
    }
    reg = (struct regulator *)malloc(sizeof(struct regulator));
    if (!reg) {
        return (struct regulator *)NULL;
    }

    (void)LOS_MuxPend(rdev->lock, LOS_WAIT_FOREVER);
    reg->rdev = rdev;
    reg->max_uV = rdev->constraints->max_uV;
    reg->min_uV = rdev->constraints->min_uV;

    LOS_ListTailInsert(&g_regdev_list, &reg->list);

    reg->supply_name = (char *)malloc(REG_BUF_SIZE);
    if (reg->supply_name == NULL) {
        goto overflow_err;
    }

    memcpy_s(reg->supply_name, REG_BUF_SIZE, supply_name, REG_BUF_SIZE);

    (void)LOS_MuxPost(rdev->lock);
    return reg;

overflow_err:
    LOS_ListDelete(&reg->list);
    free(reg);
    (void)LOS_MuxPost(rdev->lock);
    return (struct regulator *)NULL;
}





struct regulator *regulator_get(const char *id)
{
    struct regulator_dev *rdev;
    struct regulator *reg = (struct regulator *)NULL;
    int ret = 0;

    if (!id) {
        dprintf("get() with no identifier\n");
        return (struct regulator *)NULL;
    }

    (void)LOS_MuxPend(__regulator_list_lock, LOS_WAIT_FOREVER);

    rdev = regulator_dev_lookup(id, &ret);
    if (rdev) {
        goto found;
    }

    if (ret && ret != -ENODEV) {
        (void)LOS_MuxPost(__regulator_list_lock);
        return (struct regulator *)NULL;
    }

    if (have_full_constraints()) {
        PRINT_ERR("supply %s not found, using dummy regulator\n", id);

        rdev = dummy_regulator_rdev;
        goto found;
    }

    (void)LOS_MuxPost(__regulator_list_lock);
    return (struct regulator *)NULL;

found:
    reg = create_regulator(rdev, id);
    (void)LOS_MuxPost(__regulator_list_lock);
    return reg;
}


void regulator_put(struct regulator *reg)
{
    if (!reg) {
        return;
    }

    (void)LOS_MuxPend(__regulator_list_lock, LOS_WAIT_FOREVER);
    LOS_ListDelete(&reg->list);
    free(reg->supply_name);
    reg->supply_name = (char *)NULL;

    free(reg);
    (void)LOS_MuxPost(__regulator_list_lock);
}




int regulator_init(void)
{
    int ret = 0;

    (void)LOS_MuxCreate(&__regulator_list_lock);

    LOS_ListInit(&g_regdev_list);
    LOS_ListInit(&g_regconsum_list);


    return ret;
}

/* init early to allow our consumers to complete system booting */
/*lint -e10 -e528*/
postcore_initcall(regulator_init);
/*lint +e10 +e528*/

#ifdef __cplusplus
#if __cplusplus
}
#endif /* __cplusplus */
#endif

